﻿using System;
using System.IO.Ports;

namespace Eric.SerialPorts
{
    /// <summary>
    /// The class is a wrapper of System.IO.Ports.SerialPort.
    /// When send a data, it will add data header , data ender and check sum number automatically.
    /// When receive a command, it will make sure data intergrity by check data header, data ender, and check sum number.
    /// </summary>
    public class SerialPortWrapper : ISerialPortWrapper
    {
        #region Properties
        private readonly SerialPort _serialPort;
        private readonly DataVerifier _dataVerifier;
        private readonly ISerialPortParameter _portParameter;
        #endregion

        #region Constructors
        /// <summary>
        ///     Initializes a new instance of the DIH.SerialPorts.SerialPortWrapper class using
        ///     the specified port name, baud rate, parity bit, data bits, stop bit, read time out, and write time out.
        /// </summary>
        /// <param name="portParameter"> 
        ///   Parameter of serial port.
        /// </param>
        /// <param name="dataVerifier">
        ///   The data verfier to use.
        /// </param>
        /// <exception >
        ///   System.IO.IOException:
        ///      The specified port could not be found.
        /// </exception>
        public SerialPortWrapper(ISerialPortParameter portParameter, DataVerifier dataVerifier)
        {
            _portParameter = portParameter;
            _dataVerifier = dataVerifier;

            _dataVerifier.BizCommandReceivedEvent += DataVerifierBizCommandReceivedEvent;
            _serialPort = new SerialPort(_portParameter.PortName, _portParameter.BaudRate, _portParameter.Parity, _portParameter.DataBits, _portParameter.StopBits) { ReadTimeout = _portParameter.ReadTimeOut, WriteTimeout = _portParameter.WriteTimeOut };
            _serialPort.DataReceived += SerialPortDataReceived;
            _serialPort.ErrorReceived += SerialPortErrorReceived;
        }
        #endregion

        #region Event Handlers From Inner Objects.
        void SerialPortErrorReceived(object sender, SerialErrorReceivedEventArgs e)
        {
            #region Required
            if (_isDisposed)
            {
                Close();
                return;
            }
            #endregion
            RaiseErrorReceived(e);
        }

        void SerialPortDataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            #region Required
            if (_isDisposed)
            {
                Close();
                return;
            }
            #endregion

            int dataLen = _serialPort.BytesToRead;
            var receivedData = new byte[dataLen];
            _serialPort.Read(receivedData, 0, dataLen);
            _dataVerifier.AddRawData(receivedData);
            _dataVerifier.StartProcess();
        }
        void DataVerifierBizCommandReceivedEvent(object sender, SerialPortWrapperDataEventArgs e)
        {
            #region Required
            if (_isDisposed)
            {
                Close();
                return;
            }
            #endregion
            RaiseBizeCommandReceived(e);
        }
        #endregion

        #region ISerialPortWrapper Public Members
        public ISerialPortParameter PortParameter { get { return _portParameter; } }
        public bool IsOpen { get { return _serialPort != null && _serialPort.IsOpen; } }
        #endregion

        #region ISerialPortWrapper Public Methods
        /// <summary>
        /// Open the serial port wrapper.
        /// </summary>
        public void Open()
        {
            _serialPort.Open();
            _dataVerifier.StartProcessDataThread();
        }

        /// <summary>
        /// Close the serial port wrapper.
        /// </summary>
        public void Close()
        {
            _dataVerifier.ExitProcess();
            _dataVerifier.BizCommandReceivedEvent -= DataVerifierBizCommandReceivedEvent;
            _dataVerifier.Dispose();

            if (_serialPort == null)
                return;
            if (!_serialPort.IsOpen)
                return;
            _serialPort.DataReceived -= SerialPortDataReceived;
            _serialPort.ErrorReceived -= SerialPortErrorReceived;
            _serialPort.Close();
            _serialPort.Dispose();
        }

        public void DiscardInAndOutBuffer()
        {
            if (_serialPort == null)
                return;
            if (!_serialPort.IsOpen)
                return;
            _serialPort.DiscardInBuffer();
            _serialPort.DiscardOutBuffer();
        }

        /// <summary>
        /// Send byte array message.
        /// </summary>
        /// <param name="bytes">Byte array to send.</param>
        public void Send(byte[] bytes)
        {
            Send(bytes, 0, bytes.Length);
        }
        /// <summary>
        /// Send a byte arrary specified start index and length.
        /// </summary>
        /// <param name="bytes">Byte array to send.</param>
        /// <param name="startIndex">Start index.</param>
        /// <param name="length">Length.</param>
        public void Send(byte[] bytes, int startIndex, int length)
        {
            #region IsRequired
            if (bytes == null)
                throw new ArgumentNullException("bytes");
            if (startIndex < 0 || length > bytes.Length)
            {
                throw new ArgumentOutOfRangeException("startIndex", "The start index is invalid.");
            }
            #endregion
            if (_serialPort == null || !_serialPort.IsOpen)
                throw new InvalidOperationException("The serial port is null or closed.");

            var bytesToSend = bytes;
            if (startIndex != 0 || length != bytes.Length)
            {
                Array.Copy(bytes, startIndex, bytesToSend, 0, length);
            }
            var encodedBytes = _dataVerifier.EncodeBizData(bytesToSend);
            _serialPort.Write(encodedBytes, 0, encodedBytes.Length);
        }
        #endregion

        #region ISerialPortWrapper Events
        public event EventHandler<SerialPortWrapperDataEventArgs> BizCommandReceivedEvent;
        private void RaiseBizeCommandReceived(SerialPortWrapperDataEventArgs e)
        {
            if (BizCommandReceivedEvent != null)
            {
                BizCommandReceivedEvent(this, e);
            }
        }

        public event EventHandler<SerialErrorReceivedEventArgs> ErrorReceived;
        private void RaiseErrorReceived(SerialErrorReceivedEventArgs e)
        {
            if (ErrorReceived != null)
            {
                ErrorReceived(this, e);
            }
        }
        #endregion

        #region IDisposable
        private bool _isDisposed;
        public void Dispose()
        {
            _isDisposed = true;
            Close();
        }
        #endregion

    }

}
